import {
  isNull,
  StringIndexer,
  PageParams,
  PagedList,
  isUndefined,
  canUse,
} from "mmb";
import * as syncpb from "../grpc-client/service/sync_pb";
import {
  getBgsSyncOpSafely,
  getMetadata,
  getOptionsOfLight,
  wrapperGrpcError,
} from "../grpc-client/grpcAdapter";
import GrpcErrCode from "../grpc-client/grpcErrCode";
import {
  SyncLocalObj,
  SyncLocalPKName,
  SyncLocalFields,
  SyncRemoteObj,
  SyncRemotePKName,
  SyncRemoteFields,
} from "../bg-enums/sync";
import { getCache, setCache, makeEntityKey } from "../proxy/redisProxy";

const getSyncMappingRemotePkByLocalObjAndLocalPkCachelly = async (
  p: GetSyncMappingByLocalObjAndLocalPkParams
): Promise<string> => {
  const k = makeSyncMappingCacheKey(`${p.localObj}_${p.localPk}`);
  const cv = await getCache(k);
  let remotePk: string;
  if (canUse(cv)) {
    console.log("syncMapping use cached value");
    remotePk = cv!;
  } else {
    console.log("syncMapping use query");
    const syncMapping = await getSyncMappingByLocalObjAndLocalPk(p);
    console.log('p=>', JSON.stringify(p))
    console.log('syncMapping=>', syncMapping)
    remotePk = syncMapping!.remotePK;
    await setCache(k, remotePk); // replay=>OK
  }
  return remotePk
};

export const getBrandRemotePkCachelly = async(localPk: string) => {
  return await getSyncMappingRemotePkByLocalObjAndLocalPkCachelly({localObj: SyncLocalObj.BRAND, localPk})
}

export const getBrandWarehouseRemotePkCachelly = async(localPk: string) => {
  return await getSyncMappingRemotePkByLocalObjAndLocalPkCachelly({localObj: SyncLocalObj.BRAND_WAREHOUSE, localPk})
}

/****************************************************************************************/
/*                                   sync entity                                        */
/****************************************************************************************/

export interface Sync {
  syncId: string;
  localObj: SyncLocalObj;
  localPKName: SyncLocalPKName;
  localFields: string[];
  remoteObj: SyncRemoteObj;
  remotePKName: SyncRemotePKName;
  remoteFields: string[];
}

const syncPbToObject = (pb: syncpb.SyncEntity): Sync => {
  return {
    syncId: `${pb.getId()}`,
    localObj: pb.getLocalObj() as SyncLocalObj,
    localPKName: pb.getLocalPkName() as SyncLocalPKName,
    localFields: pb.getLocalFieldsList(),
    remoteObj: pb.getRemoteObj() as SyncRemoteObj,
    remotePKName: pb.getRemotePkName() as SyncRemotePKName,
    remoteFields: pb.getRemoteFieldsList(),
  };
};

/****************************************************************************************/
/*                                   sync cmd                                           */
/****************************************************************************************/

export interface UpsertSyncParams {
  localObj: SyncLocalObj;
  localPKName: SyncLocalPKName;
  localFields: string[];
  remoteObj: SyncRemoteObj;
  remotePKName: SyncRemotePKName;
  remoteFields: string[];
}

export const UpsertSync = async ({
  localObj,
  localPKName,
  localFields,
  remoteObj,
  remotePKName,
  remoteFields,
}: UpsertSyncParams): Promise<Sync> => {
  const cl = getBgsSyncOpSafely();
  const req = new syncpb.UpsertSyncRequest();

  req.setLocalObj(localObj);
  req.setLocalPkName(localPKName);
  req.setLocalFieldsList(localFields);
  req.setRemoteObj(remoteObj);
  req.setRemotePkName(remotePKName);
  req.setRemoteFieldsList(remoteFields);

  return new Promise((resolve, reject) => {
    cl.upsertSync(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(syncPbToObject(res.getSync()!));
    });
  });
};

/****************************************************************************************/
/*                                   sync query                                         */
/****************************************************************************************/

export interface GetSyncByLocalObjAndLocalPkParams {
  localObj: SyncLocalObj;
  localPKName: SyncLocalPKName;
}

export const getSyncOfBrandSafely = async (): Promise<Sync> => {
  const localObj = SyncLocalObj.BRAND;
  const localPKName = SyncLocalPKName.BRAND;

  let sync = await getSyncByLocalObjAndLocalPKName({ localObj, localPKName });
  if (!isUndefined(sync)) {
    return sync;
  }
  const localFields = SyncLocalFields.BRAND.split(",");
  const remoteObj = SyncRemoteObj.BRAND;
  const remotePKName = SyncRemotePKName.BRAND;
  const remoteFields = SyncRemoteFields.BRAND.split(",");
  sync = await UpsertSync({
    localObj,
    localPKName,
    localFields,
    remoteObj,
    remotePKName,
    remoteFields,
  });
  return sync;
};

export const getSyncOfBrandWarehouseSafely = async (): Promise<Sync> => {
  const localObj = SyncLocalObj.BRAND_WAREHOUSE;
  const localPKName = SyncLocalPKName.BRAND_WAREHOUSE;

  let sync = await getSyncByLocalObjAndLocalPKName({ localObj, localPKName });
  if (!isUndefined(sync)) {
    return sync;
  }
  const localFields = SyncLocalFields.BRAND.split(",");
  const remoteObj = SyncRemoteObj.BRAND;
  const remotePKName = SyncRemotePKName.BRAND;
  const remoteFields = SyncRemoteFields.BRAND.split(",");
  sync = await UpsertSync({
    localObj,
    localPKName,
    localFields,
    remoteObj,
    remotePKName,
    remoteFields,
  });
  return sync;
};

const getSyncByLocalObjAndLocalPKName = async ({
  localObj,
  localPKName,
}: GetSyncByLocalObjAndLocalPkParams): Promise<Sync | undefined> => {
  const cl = getBgsSyncOpSafely();
  const req = new syncpb.GetSyncByLocalObjAndLocalPkNameRequest();

  req.setLocalObj(localObj);
  req.setLocalPkName(localPKName);

  return new Promise((resolve, reject) => {
    cl.getSyncByLocalObjAndLocalPkName(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          if (opRet.getCode() === GrpcErrCode.NOT_FOUND) {
            resolve(undefined);
            return;
          } else {
            reject(wrapperGrpcError(opRet.getMsg()));
            return;
          }
        }
        resolve(syncPbToObject(res.getSync()!));
      }
    );
  });
};

/****************************************************************************************/
/*                                   sync_mapping entity                                */
/****************************************************************************************/

const makeSyncMappingCacheKey = makeEntityKey("sync_mapping");

export interface SyncMapping {
  syncMappingId: string;
  syncId: string;
  localObj: SyncLocalObj;
  localPK: string;
  remoteObj: SyncRemoteObj;
  remotePK: string;
}

const syncMappingPbToObject = (pb: syncpb.SyncMappingEntity): SyncMapping => {
  return {
    syncMappingId: `${pb.getId()}`,
    syncId: `${pb.getSyncId()}`,
    localObj: pb.getLocalObj() as SyncLocalObj,
    localPK: pb.getLocalPk(),
    remoteObj: pb.getRemoteObj() as SyncRemoteObj,
    remotePK: pb.getRemotePk(),
  };
};

/****************************************************************************************/
/*                                   sync_mapping query                                 */
/****************************************************************************************/

export interface GetSyncMappingByLocalObjAndLocalPkParams {
  localObj: SyncLocalObj;
  localPk: string;
}

export const getSyncMappingByLocalObjAndLocalPk = async ({
  localObj,
  localPk,
}: GetSyncMappingByLocalObjAndLocalPkParams): Promise<
  SyncMapping | undefined
> => {
  const cl = getBgsSyncOpSafely();
  const req = new syncpb.GetSyncMappingByLocalObjAndLocalPkRequest();

  req.setLocalObj(localObj);
  req.setLocalPk(localPk);

  return new Promise((resolve, reject) => {
    cl.getSyncMappingByLocalObjAndLocalPk(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          if (opRet.getCode() === GrpcErrCode.NOT_FOUND) {
            resolve(undefined);
            return;
          } else {
            reject(wrapperGrpcError(opRet.getMsg()));
            return;
          }
        }
        resolve(syncMappingPbToObject(res.getSyncMapping()!));
      }
    );
  });
};

export const listSyncMappingsBySyncID = async (
  syncId: string,
  pager: PageParams
): Promise<PagedList<SyncMapping>> => {
  const cl = getBgsSyncOpSafely();
  const req = new syncpb.ListSyncMappingsBySyncIDRequest();

  req.setLimit(pager.limit);
  req.setOffset(pager.offset);
  req.setSyncId(Number(syncId));

  return new Promise((resolve, reject) => {
    cl.listSyncMappingsBySyncID(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        const ret: PagedList<SyncMapping> = {
          total: 0,
          items: [],
        };
        ret.total = res.getTotal();
        ret.items = res
          .getSyncMappingsList()
          .map((s) => syncMappingPbToObject(s));
        resolve(ret);
      }
    );
  });
};

/****************************************************************************************/
/*                                   sync_remote entity                                 */
/****************************************************************************************/

export interface SyncRemote {
  syncRemoteId: string;
  syncId: string;
  remoteObj: SyncRemoteObj;
  remotePK: string;
  data: StringIndexer;
}

const syncRemotePbToObject = (pb: syncpb.SyncRemoteEntity): SyncRemote => {
  return {
    syncRemoteId: `${pb.getId()}`,
    syncId: `${pb.getSyncId()}`,
    remoteObj: pb.getRemoteObj() as SyncRemoteObj,
    remotePK: pb.getRemotePk(),
    data: JSON.parse(pb.getData()),
  };
};

/****************************************************************************************/
/*                                   sync_remote cmd                                    */
/****************************************************************************************/

export interface CopySyncRemoteParams {
  syncRemotes: SyncRemote[];
}

export const copySyncRemote = async ({
  syncRemotes,
}: CopySyncRemoteParams): Promise<Number> => {
  const cl = getBgsSyncOpSafely();
  const req = new syncpb.CopySyncRemoteRequest();

  req.setSyncRemotesList(
    syncRemotes.map((row) => {
      const r = new syncpb.SyncRemoteEntity();
      r.setSyncId(Number(row.syncId));
      r.setRemoteObj(row.remoteObj);
      r.setRemotePk(row.remotePK);
      r.setData(JSON.stringify(row.data));
      return r;
    })
  );

  return new Promise((resolve, reject) => {
    cl.copySyncRemote(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(res.getTotal());
    });
  });
};

/****************************************************************************************/
/*                                   sync_remote query                                  */
/****************************************************************************************/

export interface GetSyncRemoteBySyncIDAndRemotePKParams {
  syncId: string;
  remotePK: string;
}

export const getSyncBySyncIDAndRemotePK = async ({
  syncId,
  remotePK,
}: GetSyncRemoteBySyncIDAndRemotePKParams): Promise<SyncRemote | undefined> => {
  const cl = getBgsSyncOpSafely();
  const req = new syncpb.GetSyncRemoteBySyncIDAndRemotePkRequest();

  req.setSyncId(Number(syncId));
  req.setRemotePk(remotePK);

  return new Promise((resolve, reject) => {
    cl.getSyncRemoteBySyncIDAndRemotePk(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          if (opRet.getCode() === GrpcErrCode.NOT_FOUND) {
            resolve(undefined);
            return;
          } else {
            reject(wrapperGrpcError(opRet.getMsg()));
            return;
          }
        }
        resolve(syncRemotePbToObject(res.getSyncRemote()!));
      }
    );
  });
};

export const listSyncRemotesBySyncID = async (
  syncId: string,
  pager: PageParams
): Promise<PagedList<SyncRemote>> => {
  const cl = getBgsSyncOpSafely();
  const req = new syncpb.ListSyncRemotesBySyncIDRequest();

  req.setLimit(pager.limit);
  req.setOffset(pager.offset);
  req.setSyncId(Number(syncId));

  return new Promise((resolve, reject) => {
    cl.listSyncRemotesBySyncID(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        const ret: PagedList<SyncRemote> = {
          total: 0,
          items: [],
        };
        ret.total = res.getTotal();
        ret.items = res
          .getSyncRemotesList()
          .map((s) => syncRemotePbToObject(s));
        resolve(ret);
      }
    );
  });
};
